package com.linb.pan.server.common.aspect;

import com.linb.pan.cache.core.constants.CacheConstants;
import com.linb.pan.core.response.R;
import com.linb.pan.core.response.ResponseCode;
import com.linb.pan.core.utils.JwtUtil;
import com.linb.pan.server.common.annotation.LoginIgnore;
import com.linb.pan.server.common.utils.UserIdUtil;
import com.linb.pan.server.modules.user.constants.UserConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;

@Component
@Aspect
@Slf4j
public class CommonLoginAspect {

    /**
     * 登录认证参数名称
     */
    private static final String LOGIN_AUTH_PARAM_NAME = "authorization";

    /**
     * 请求头登录认证key
     */
    private static final String LOGIN_AUTH_HEADER_NAME = "Authorization";

    /**
     * 切点表达式: 任意返回值 controller任意包或子包 任意方法
     */
    private static final String POINT_CUT = "execution(* com.linb.pan.server.modules.*.controller..*(..))";

    @Autowired
    private CacheManager cacheManager;

    /**
     * 切点模板方法
     */
    @Pointcut(value = POINT_CUT)
    public void loginAuth() {

    }


    @Around("loginAuth()")
    public Object loginAuthAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //1.判断是否需要校验登录信息，只要加了注解LoginIgnore就不校验
        boolean isCheck = checkNeedCheckLoginInfo(proceedingJoinPoint);
        if (isCheck) {
            //1.1 获取请求路径
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            String requestURI = request.getRequestURI();
            log.info("成功拦截到请求，URI = {}", requestURI);
            //1.2 校验token有效性
            boolean isValidToken = checkAndSaveUserId(request);
            if (!isValidToken) {
                //1.2.1 校验失败，未登录
                log.warn("成功拦截到请求，URL = {}，未登录", requestURI);
                return R.fail(ResponseCode.NEED_LOGIN);
            }
            log.info("成功拦截到请求，URL = {}，登录成功", requestURI);
        }
        //2.如果不需要直接放行
        return proceedingJoinPoint.proceed();
    }

    /**
     * 校验token有效性
     *
     * @param request
     * @return
     */
    private boolean checkAndSaveUserId(HttpServletRequest request) {
        //先从请求头中获取，再从参数获取
        String accessToken = request.getHeader(LOGIN_AUTH_HEADER_NAME);
        if (StringUtils.isBlank(accessToken)) {
            accessToken = request.getParameter(LOGIN_AUTH_PARAM_NAME);
        }
        if (StringUtils.isBlank(accessToken)) {
            return false;
        }
        //校验token
        Object userId = JwtUtil.analyzeToken(accessToken, UserConstants.LOGIN_USER_ID);
        if (userId == null) {
            return false;
        }
        Cache cache = cacheManager.getCache(CacheConstants.B_PAN_CACHE_NAME);
        String redisAccessToken = cache.get(UserConstants.USER_LOGIN_PREFIX + userId, String.class);
        if (redisAccessToken == null) {
            return false;
        }
        //保存userId到ThreadLocal
        if (Objects.equals(redisAccessToken, accessToken)) {
            saveUserId(Long.valueOf(String.valueOf(userId)));
            return true;
        }
        return false;
    }

    private void saveUserId(Long userId) {
        UserIdUtil.set(userId);
    }

    /**
     * 判断是否需要校验登录信息，只要加了注解LoginIgnore就不校验
     *
     * @param proceedingJoinPoint
     * @return
     */
    private boolean checkNeedCheckLoginInfo(ProceedingJoinPoint proceedingJoinPoint) {
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        return !method.isAnnotationPresent(LoginIgnore.class);
    }

}
